namespace BM.Tools.WebControls.MForm.Generator
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Web.UI;
    using System.Xml;
    using System.Xml.Schema;

    using BM.Tools.WebControls.MForm.Generator.Templates;
    using BM.Tools.Xml.Schema;

    /// <summary>
    /// The form builder class is an implementation of the <see cref="SchemaTraverser" /> class
    /// that goes through the xml schema definitions and collects <see cref="Visited" /> objects and references between them
    /// </summary>
    public class FormBuilder : SchemaTraverser
    {                
        private FormItem rootElement;
        
        private FormBuilderSettings settings = new FormBuilderSettings();               

        /// <summary>
        /// Initializes a new instance of the <see cref="FormBuilder"/> class. 
        /// </summary>
        /// <param name="xs">
        /// The xml schema set.
        /// </param>
        /// <param name="terms">
        /// The terms.
        /// </param>
        /// <param name="mode">
        /// The form builder mode.
        /// </param>
        public FormBuilder(XmlSchemaSet xs, Dictionary<string, string> terms, FormBuilderMode mode) : base(xs, terms)
        {
            Initialize(xs, mode);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="FormBuilder"/> class. 
        /// </summary>
        /// <param name="xs">
        /// The xml schema set.
        /// </param>
        /// <param name="mode">
        /// The form builder mode.
        /// </param>
        public FormBuilder(XmlSchemaSet xs, FormBuilderMode mode) : base(xs)
        {
            Initialize(xs, mode);
        }

        /// <summary>
        /// Gets Mode.
        /// </summary>
        /// <value>
        /// The form builder mode.
        /// </value>
        public FormBuilderMode Mode
        {
            get;
            internal set;
        }

        /// <summary>
        /// Gets RootElement.
        /// </summary>
        /// <value>
        /// The root element.
        /// </value>
        public FormItem RootElement
        {
            get
            {
                return rootElement;
            }
        }

        /// <summary>
        /// Gets SchemaSet.
        /// </summary>
        /// <value>
        /// The schema set.
        /// </value>
        public XmlSchemaSet SchemaSet
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets Settings.
        /// </summary>
        /// <value>
        /// The settings.
        /// </value>
        public FormBuilderSettings Settings
        {
            get
            {
                return settings;
            }

            internal set
            {
                settings = value;
            }
        }

        /// <summary>
        /// gets output template out of the xml model
        /// </summary>
        /// <returns>
        /// the item template
        /// </returns>
        [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "The operation may be expensive")]
        public ITemplate GetTemplate()
        {
            return new ItemTemplate(rootElement);
        }

        /// <summary>
        /// Traverses a xml schema element
        /// </summary>
        /// <param name="xse">
        /// The xml schema element.
        /// </param>
        /// <returns>
        /// the Visited object
        /// </returns>
        public override Visited TraverseElement(XmlSchemaElement xse)
        {
            rootElement = (FormItem)CreateCurrent(null, xse);
            TraverseElement(xse, rootElement);
            RemovePending();
            return rootElement;
        }

        /// <summary>
        /// A factory method for creating Visited objects based on the xml schema object and the parent visited object
        /// </summary>
        /// <param name="parent">
        /// The parent.
        /// </param>
        /// <param name="xso">
        /// The xml schema object.
        /// </param>
        /// <returns>
        /// the visited object
        /// </returns>
        protected override Visited CreateCurrent(Visited parent, XmlSchemaObject xso)
        {
            var choice = xso as XmlSchemaChoice;
            if (choice != null)
            {
                return new FormChoice(this, parent, choice);
            }

            var element = xso as XmlSchemaElement;
            if (element != null)
            {
                var complexType = element.ElementSchemaType as XmlSchemaComplexType;
                if (element.IsAbstract || (complexType != null && complexType.IsAbstract))
                {
                    return new FormChoice(this, parent, element);
                }

                if (element.RefName.IsEmpty)
                {
                    return new FormElement(this, parent, element);
                }
            }

            XmlSchemaAttribute xsa = xso as XmlSchemaAttribute;
            if (xsa != null)
            {
                if (xsa.RefName.IsEmpty)
                {
                    return new FormAttribute(this, parent, xsa);
                }
            }

            return new FormVisited(this, parent, xso);
        }

        /// <summary>
        /// Traverses base type
        /// </summary>
        /// <param name="xqn">
        /// The base typee's qualified name
        /// </param>
        /// <param name="v">
        /// The visited object.
        /// </param>
        protected override void TraverseBaseType(XmlQualifiedName xqn, Visited v)
        {
            var formVisited = (FormVisited)v;
            var formItem = formVisited.FormItem;
            formItem.DataType = (XmlTypeCode)Enum.Parse(typeof(XmlTypeCode), xqn.Name, true);
            if (formItem.DataType.Equals(XmlTypeCode.Date))
            {
                formItem.MaxLength = 11;
            }

            formItem.IsLeaf = true;
        }

        /// <summary>
        /// Traverses the choice element
        /// </summary>
        /// <param name="xsc">
        /// The xml schema choice.
        /// </param>
        /// <param name="v">
        /// The visited object.
        /// </param>
        protected override void TraverseChoice(XmlSchemaChoice xsc, Visited v)
        {
            for (int i = 0; i < xsc.Items.Count; i++)
            {
                var schemaAnnotated = xsc.Items[i] as XmlSchemaAnnotated;
                TraverseAnnotated(schemaAnnotated, v);
            }
        }

        /// <summary>
        /// Traverses complex content restriction
        /// </summary>
        /// <param name="xsccr">
        /// The xml schema complex content restriction.
        /// </param>
        /// <param name="v">
        /// The visited object.
        /// </param>
        protected override void TraverseComplexContentRestriction(XmlSchemaComplexContentRestriction xsccr, Visited v)
        {
            TraverseAttributes(xsccr.Attributes, v);
            TraverseAnnotated(xsccr.Particle, v);
        }

        /// <summary>
        /// Traverses enumeration facet
        /// </summary>
        /// <param name="x">
        /// The enumeration facet.
        /// </param>
        /// <param name="v">
        /// The visited object.
        /// </param>
        protected override void TraverseEnumerationFacet(XmlSchemaEnumerationFacet x, Visited v)
        {
            var formVisited = (FormVisited)v;
            var formItem = formVisited.FormItem;
            formItem.Options.Add(x.Value);
        }

        /// <summary>
        /// Traverses facer
        /// </summary>
        /// <param name="xsf">
        /// The xml schema facet.
        /// </param>
        /// <param name="v">
        /// The visited object.
        /// </param>
        protected override void TraverseFacet(XmlSchemaFacet xsf, Visited v)
        {
            var enumerationFacet = xsf as XmlSchemaEnumerationFacet;
            if (enumerationFacet != null)
            {
                TraverseEnumerationFacet(enumerationFacet, v);
                return;
            }

            var formVisited = (FormVisited)v;
            var formItem = formVisited.FormItem;
            string typename = xsf.GetType().Name;
            string type = typename.Substring(9, typename.Length - 14);            
            formItem.AddRestriction(type, xsf.Value);
            if (xsf is XmlSchemaMaxLengthFacet || xsf is XmlSchemaLengthFacet)
            {
                formItem.MaxLength = Convert.ToInt32(xsf.Value, CultureInfo.InvariantCulture);
            }
        }

        /// <summary>
        /// Traverses identity constraint
        /// </summary>
        /// <param name="xsic">
        /// The xml schema identity constraint
        /// </param>
        /// <param name="v">
        /// The visited object.
        /// </param>
        protected override void TraverseIdentityConstraint(XmlSchemaIdentityConstraint xsic, Visited v)
        {
            var formVisited = (FormVisited)v;
            var formItem = formVisited.FormItem;
            formItem.IdentityConstraints.Add(xsic);
        }

        /// <summary>        
        /// Sets the schema set and builder mode
        /// </summary>
        /// <param name="xs">
        /// The schema set.
        /// </param>
        /// <param name="mode">
        /// The builder mode.
        /// </param>
        private void Initialize(XmlSchemaSet xs, FormBuilderMode mode)
        {
            SchemaSet = xs;
            Mode = mode;
        }
    }
}